﻿using System;
using System.Linq;
using System.IO;
using System.Collections.Generic;
using OpenTK;
using OpenTK.Graphics.OpenGL;

namespace Utils
{
    public static class ObjLoader
    {

        private static string cfm(string str)
        {
            return str;
            //return str.Replace('.', ',');
        }

        public static MeshData LoadStream(string file)
        {
            string filePath = Directory.GetCurrentDirectory() + "\\Models\\" + file;
            var stream = File.OpenRead(filePath);
            var reader = new StreamReader(stream);

            var points = new List<Vector3d>();
            var normals = new List<Vector3d>();
            var texCoords = new List<Vector2d>();
            var faces = new List<int[]>();

            string line;
            char[] splitChars = { ' ' };
            while ((line = reader.ReadLine()) != null)
            {
                line = line.Trim(splitChars);
                line = line.Replace("  ", " ");

                string[] parameters = line.Split(splitChars);

                switch (parameters[0])
                {
                    case "p": // Point
                        break;

                    case "v": // Vertex
                        double x = double.Parse(cfm(parameters[1]));
                        double y = double.Parse(cfm(parameters[2]));
                        double z = double.Parse(cfm(parameters[3]));
                        points.Add(new Vector3d(x, y, z));
                        break;

                    case "vt": // TexCoord
                        double u = double.Parse(cfm(parameters[1]));
                        double v = double.Parse(cfm(parameters[2]));
                        texCoords.Add(new Vector2d(u, v));
                        break;

                    case "vn": // Normal
                        double nx = double.Parse(cfm(parameters[1]));
                        double ny = double.Parse(cfm(parameters[2]));
                        double nz = double.Parse(cfm(parameters[3]));
                        normals.Add(new Vector3d(nx, ny, nz));
                        break;

                    case "f": // Face
                        int p1 = int.Parse(cfm(parameters[1]));
                        int p2 = int.Parse(cfm(parameters[2]));
                        int p3 = int.Parse(cfm(parameters[3]));
                        faces.Add(new[] { p1, p2, p3 });
                        break;
                }
            }

            Vector3d[] p = points.ToArray();
            Vector2d[] tc = texCoords.ToArray();

            return new MeshData(p, normals, tc, faces);
        }

        private static Vector3d CalcNormal(int i, IEnumerable<int[]> faces, List<Vector3d> points)
        {
            var normals = new List<Vector3d>();
            foreach (var face in faces.Where(f => f.Contains(i)))
            {

                int one = face[0] == i ? 1 : face[1] == i ? 2 : 0;
                int two = face[0] == i ? 2 : face[1] == i ? 0 : 1;

                var v12 = new Vector3d(points[face[one] - 1].X - points[i - 1].X, points[face[one] - 1].Y - points[i - 1].Y, points[face[one] - 1].Z - points[i - 1].Z);
                var v14 = new Vector3d(points[face[two] - 1].X - points[i - 1].X, points[face[two] - 1].Y - points[i - 1].Y, points[face[two] - 1].Z - points[i - 1].Z);

                normals.Add(Vector3d.Cross(v12, v14));
            }

            double x = 0;
            double y = 0;
            double z = 0;

            foreach (var d in normals)
            {
                x += d.X;
                y += d.Y;
                z += d.Z;
            }

            return Vector3d.Normalize(new Vector3d(x, y, z));
        }
    }

    [Serializable]
    public class MeshData
    {
        public Vector3d[] Points;
        public List<Vector3d> Normals;
        public Vector2d[] TextureCoords;
        public List<int[]> Faces;
        public Point3D Offset;
        [NonSerialized]
        public BoundingBox Box;

        public double Angle;

        public MeshData(Vector3d[] points, List<Vector3d> normals, Vector2d[] textureCoords, List<int[]> faces)
        {
            Points = points;
            Normals = normals;
            TextureCoords = textureCoords;
            Faces = faces;
        }

        public void DrawMesh()
        {
            GetBox();

            GL.PushMatrix();

            if (Offset != null)
            {
                GL.Translate(Offset.X, Offset.Y, Offset.Z);
                GL.Rotate(Angle, Vector3d.UnitY);
                //var eye = new Vector3d(Offset.X, Offset.Y + 10, Offset.Z);
                //var target = new Vector3d(Offset.X, Offset.Y + 10, Offset.Z);

                //var ang = new Angle3D();
                //var az = new Angle3D();

                //ang.AngleRadians = Angle;
                //az.AngleRadians = Angle;

                //target.Z -= ang.CosAngle * az.SinAngle;
                //target.X -= ang.SinAngle * az.SinAngle;

                //target.Y -= az.CosAngle;
                //var mat = Matrix4d.LookAt(eye, target, Vector3d.UnitY);
                //GL.LoadMatrix(ref mat);
            }
            GL.Begin(BeginMode.Triangles);
            foreach (var face in Faces)
            {
                foreach (var i in face)
                {
                    var point = Points[i - 1];

                    if (Normals.Count < i)
                    {
                        for (int j = Normals.Count; j < i; j++)
                        {
                            Normals.Add(Vector3d.Zero);
                        }
                    }

                    if (Normals[i - 1] == Vector3d.Zero)
                        Normals[i - 1] = CalcNormal(i);

                    GL.Normal3(Normals[i - 1]);
                    GL.Vertex3(point);
                }
            }
            GL.End();
            GL.PopMatrix();
        }

        private Vector3d CalcNormal(int i)
        {
            var normals = new List<Vector3d>();
            foreach (var face in Faces)
            {
                var cont = true;
                foreach (var vert in face)
                {
                    if (vert == i)
                        cont = false;
                }

                if (cont) continue;
                int one = face[0] == i ? 1 : face[1] == i ? 2 : 0;
                int two = face[0] == i ? 2 : face[1] == i ? 0 : 1;

                var v12 = new Vector3d(Points[face[one] - 1].X - Points[i - 1].X, Points[face[one] - 1].Y - Points[i - 1].Y, Points[face[one] - 1].Z - Points[i - 1].Z);
                var v14 = new Vector3d(Points[face[two] - 1].X - Points[i - 1].X, Points[face[two] - 1].Y - Points[i - 1].Y, Points[face[two] - 1].Z - Points[i - 1].Z);

                normals.Add(Vector3d.Cross(v12, v14));
            }

            double x = 0;
            double y = 0;
            double z = 0;

            foreach (var d in normals)
            {
                x += d.X;
                y += d.Y;
                z += d.Z;
            }

            var normal = new Vector3d(x, y, z);

            return Vector3d.Normalize(normal);
        }
        private void GetBox()
        {

            double maxX = double.MinValue;
            double minX = double.MaxValue;

            double maxY = double.MinValue;
            double minY = double.MaxValue;

            double maxZ = double.MinValue;
            double minZ = double.MaxValue;

            foreach (var point3D in Points)
            {
                if (maxX < point3D.X)
                    maxX = point3D.X;

                if (minX > point3D.X)
                    minX = point3D.X;

                if (maxY < point3D.Y)
                    maxY = point3D.Y;

                if (minY > point3D.Y)
                    minY = point3D.Y;

                if (maxZ < point3D.Z)
                    maxZ = point3D.Z;

                if (minZ > point3D.Z)
                    minZ = point3D.Z;
            }

            double width = (maxX - minX);
            double height = (maxY - minY);
            double depth = (maxZ - minZ);

            var center = new Point3D(maxX - width / 2, maxY - height / 2, maxZ - depth / 2);

            if (Offset != null)
            {
                center.X += Offset.X;
                center.Y += Offset.Y;
                center.Z += Offset.Z;
            }

            Box = new BoundingBox(center, height, width, depth);
        }
    }
}